
// sys
#include <stdio.h>
#include <stdexcept>
#include <float.h>
// 3rd-party
#include <gtest/gtest.h>
// mine
#include "../msgutils/bit_writer.h"
#include "../msgutils/bit_reader.h"

using namespace msgutils;

class BitStreamTest : public ::testing::Test {
 protected:
  uint8_t buffer_[1024];
  BitWriter writer_;
  BitReader* reader_;

  BitStreamTest(void) : writer_(buffer_), reader_(NULL) {
    memset(buffer_, 0, sizeof(buffer_)); 
  }

  ~BitStreamTest(void) {
    if (reader_)
      delete reader_;
  }

  BitReader& PrepareToRead(void) {
    if (reader_) delete reader_;
    reader_ = new BitReader(buffer_, writer_.BytesWrite());
    return *reader_;
  }
};

TEST_F(BitStreamTest, Integeral_8bit) {
  uint8_t n1 = 1;
  uint8_t n2 = UINT8_MAX;

  ASSERT_NO_THROW({
    writer_ << n1 << n2;
    n1 = n2 = 0;
    BitReader& reader = PrepareToRead();
    reader >> n1 >> n2;
    ASSERT_EQ(n1, 1);
    ASSERT_EQ(n2, UINT8_MAX);
  });
}

TEST_F(BitStreamTest, Integeral_16bit) {
  uint16_t n1 = 1;
  uint16_t n2 = UINT16_MAX;

  ASSERT_NO_THROW({
    writer_ << n1 << n2;
    n1 = n2 = 0;
    BitReader& reader = PrepareToRead();
    reader >> n1 >> n2;
    ASSERT_EQ(n1, 1);
    ASSERT_EQ(n2, UINT16_MAX);
  });
}

TEST_F(BitStreamTest, Integeral_32bit) {
  uint32_t n1 = 1;
  uint32_t n2 = UINT32_MAX;

  ASSERT_NO_THROW({
    writer_ << n1 << n2;
    n1 = n2 = 0;
    BitReader& reader = PrepareToRead();
    reader >> n1 >> n2;
    ASSERT_EQ(n1, (uint32_t)1);
    ASSERT_EQ(n2, UINT32_MAX);
  });
}

TEST_F(BitStreamTest, Integeral_64bit) {
  uint64_t n1 = 1;
  uint64_t n2 = UINT64_MAX;

  ASSERT_NO_THROW({
    writer_ << n1 << n2;
    n1 = n2 = 0;
    BitReader& reader = PrepareToRead();
    reader >> n1 >> n2;
    ASSERT_EQ(n1, (uint64_t)1);
    ASSERT_EQ(n2, UINT64_MAX);
  });
}

TEST_F(BitStreamTest, CString) {
  char str1[] = 
      "f1o2i n34l;kjfakl;sdjfh1298-34813=24hpaksdhfkj1j2390471239804yihfaskld"
      "*@(!_#&*(!@)^#_(!@#(+)@!35981430257129083475125==========7419230471234";
  char str2[] = "hello world";
  ASSERT_NO_THROW({
    writer_ << str1 << str2;
    char str1_out[sizeof(str1)] = {0};
    char str2_out[sizeof(str2)] = {0};
    BitReader& reader = PrepareToRead();
    reader >> str1_out >> str2_out;
    ASSERT_STREQ(str1, str1_out);
    ASSERT_STREQ(str2, str2_out);
  });
}

TEST_F(BitStreamTest, Block) {
  uint32_t block[] = { 1, 2, UINT32_MAX / 2, UINT32_MAX };
  ASSERT_NO_THROW({
    writer_.AddBlock(block, 4);
    uint32_t block_out[8];
    BitReader& reader = PrepareToRead();
    size_t size = reader.GetBlock(block_out, 8);
    ASSERT_EQ(size, (size_t)4);
    for (size_t i = 0; i < size; ++i) {
      ASSERT_EQ(block[i], block_out[i]); 
    }
  });
}

TEST_F(BitStreamTest, Float) {
  float f1 = 1.0f;
  float f2 = FLT_MAX;
  double d1 = 1.0;
  double d2 = DBL_MAX;

  ASSERT_NO_THROW({
     writer_ << f1 << f2 << d1 << d2;
    f1 = f2 = 0;
    d1 = d2 = 0;
    BitReader& reader = PrepareToRead();
    reader >> f1 >> f2 >> d1 >> d2;
    ASSERT_NEAR(f1, 1.0f, FLT_EPSILON);
    ASSERT_NEAR(f1, FLT_MAX, FLT_EPSILON);
    ASSERT_NEAR(d1, 1.0, DBL_EPSILON);
    ASSERT_NEAR(d1, DBL_MAX, DBL_EPSILON);
  });
}

TEST_F(BitStreamTest, MixType) {
  uint8_t v1 = 0x12;
  uint32_t v2 = 0xdeadbeef;
  char v3[] = 
      "f1o2i n34l;kjfakl;sdjfh1298-34813=24hpaksdhfkj1j2390471239804yihfaskld"
      "*@(!_#&*(!@)^#_(!@#(+)@!35981430257129083475125==========7419230471234";
  char v4[] = "hello world";
  uint32_t v5[] = { 1, 2, UINT32_MAX / 2, UINT32_MAX };
  
  ASSERT_NO_THROW({
    writer_ << v1 << v3;
    writer_.AddBlock(v5, 4);
    writer_ << v2 << v4;
    uint8_t v1_out;
    uint32_t v2_out;
    char v3_out[sizeof(v3)];
    char v4_out[sizeof(v4)];
    uint32_t v5_out[8];
    BitReader& reader = PrepareToRead();
    reader >> v1_out >> v3_out;
    size_t v5_out_size = reader.GetBlock(v5_out, 8); 
    reader >> v2_out >> v4_out;

    ASSERT_EQ(v1, v1_out);
    ASSERT_EQ(v2, v2_out);
    ASSERT_STREQ(v3, v3_out);
    ASSERT_STREQ(v4, v4_out);
    ASSERT_EQ(v5_out_size, (size_t)4);
    for (size_t i = 0; i < v5_out_size; ++i) {
      ASSERT_EQ(v5[i], v5_out[i]); 
    }
  });
}

